﻿using System;
using System.IO;
using System.Linq;
using System.Threading;
using Seagull.Configuration;
using Seagull.DAL;
using Seagull.DAL.Compress;
using Seagull.Events;
using Seagull.Generic;
using Seagull.Model.Alerts;

namespace Seagull.Jobs
{
    /// <summary>
    /// 备份工作流程：
    ///     1. 查找本地压缩后的所有日期目录
    ///     2. 若备份目录下已有同名目录则删除（可能是未完成的备份）
    ///     3. 依次复制每一天的压缩后数据，每复制完一天的数据，将该日的压缩后数据删除
    /// </summary>
    public class BackupJob : MaintenanceJob
    {
        private const string TempFolderPostfix = "_BAKTMP";

        private readonly string _compressFolder;
        private readonly string _folderNameOnRemovableDevice;
        private readonly DateTime _startTime;
        private readonly PathHelper _compressPathHelper;
        private string _backupDirectory;

        public BackupJob(string compressFolder, string folderNameOnRemovableDevice, DateTime startTime)
        {
            _compressFolder = compressFolder;
            _folderNameOnRemovableDevice = folderNameOnRemovableDevice;
            _startTime = startTime;
            _compressPathHelper = new PathHelper(compressFolder, Compresser.DefaultCompressedFileExtension);
        }

        public override void Start(CancellationToken cancellationToken)
        {
            var compressedDirs = GetCompressedDirs();

            if(!compressedDirs.Any())
            {
                return;
            }

            // 当有文件需要备份时才去寻找移动硬盘
            _backupDirectory = PathHelper.GetBackupDirectory(_folderNameOnRemovableDevice);

            var backupPathHelper = new PathHelper(_backupDirectory);
            var existingBackupDirs = backupPathHelper.DateDirectories.ToArray();

            var dirsToBackup = compressedDirs
                .Where(dir => existingBackupDirs.All(t => t.Item2 != dir.Item2))
                .Select(t => t.Item1.FullName).ToArray();

            CleanTempFolders();

            var backupedFolderCount = 0;
            foreach(var sourceFolder in dirsToBackup)
            {
                cancellationToken.ThrowIfCancellationRequested();
                var targetFolder = sourceFolder.Replace(_compressFolder, _backupDirectory);

                // 准备临时目录
                var tempTargetFolder = GenericHelper.AppendFolderName(targetFolder, TempFolderPostfix);
                if(Directory.Exists(tempTargetFolder))
                {
                    FileHelper.DeleteDirectory(tempTargetFolder);
                }

                try
                {
                    CopyDir(sourceFolder, tempTargetFolder,
                        percentage => OnProgressChanged((int)((percentage + backupedFolderCount * 100.0) / dirsToBackup.Length)),
                        cancellationToken);
                    FileHelper.MoveDirectory(tempTargetFolder, targetFolder);
                    ++backupedFolderCount;
                    // 不删除压缩以后的文件，留到清理任务中做
                    //                    FileHelper.DeleteDirectory(sourceFolder);
                }
                // 若复制目录中途取消，会抛出OperationCanceledException
                catch(OperationCanceledException)
                {
                    // 删除正在备份的目录
                    FileHelper.DeleteDirectory(tempTargetFolder);
                    throw;
                }
            }
        }

        private void CleanTempFolders()
        {
            foreach(var tempDirectory in Directory.GetDirectories(_compressFolder, "*" + TempFolderPostfix, SearchOption.TopDirectoryOnly))
            {
                try
                {
                    FileHelper.DeleteDirectory(tempDirectory);
                }
                catch(Exception exception)
                {
                    LogOnlyAlertEvent.Instance.Publish(new ApplicationAlert(exception, "删除备份临时目录"));
                }
            }
        }

        private Tuple<DirectoryInfo, DateTime>[] GetCompressedDirs()
        {
            return _compressPathHelper.DateDirectories.ToArray();
        }

        private static void CopyDir(string sourcePath, string destinationPath,
            Action<int> onProgressChanged = null,
            CancellationToken? cancellationToken = null)
        {
            onProgressChanged = onProgressChanged ?? (p => { });

            // 在开始复制前触发进度事件
            onProgressChanged(0);

            //Now Create all of the directories
            if(!Directory.Exists(destinationPath))
            {
                Directory.CreateDirectory(destinationPath);
            }
            foreach(var dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
            {
                Directory.CreateDirectory(dirPath.Replace(sourcePath, destinationPath));
            }

            // 复制所有文件
            var files = Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories);
            var copiedFileCount = 0;
            foreach(var fileName in files)
            {
                if(cancellationToken.HasValue)
                {
                    cancellationToken.Value.ThrowIfCancellationRequested();
                }
                File.Copy(fileName, fileName.Replace(sourcePath, destinationPath), true);
                onProgressChanged((int)(++copiedFileCount * 100.0 / files.Length));
            }
        }
    }
}
